/**
 * DSM - Distributed Search Manager
 * Developed by Milspec Research International Pty Ltd
 * $Author: skahl $
 * $Revision: 1.5 $
 * $Date: 2007/12/05 22:28:06 $
 * (c)Copyright 2004
 * education.au limited
 * DEST
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the names education.au limited, DEST nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 * To the extent permitted by law, the copyright owners of this software and
 * its contributors:
 * (i) exclude all warranties in relation to the software; and
 * (ii) exclude liability for all losses, costs, expenses and damages arising
 * in any way from the use of the software whether arising from or in
 * relation to breach of contract, negligence or any other tort, in equity
 * or otherwise. If the software is in breach of a warranty which is
 * implied by law, the copyright owners and contributors liability is
 * limited to the replacement of the software.
 *
 * @author	gsingh
 */

package au.edu.educationau.opensource.dsm.service;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;

import org.apache.log4j.Logger;

import au.edu.educationau.opensource.dsm.obj.SearchResultSet;

/**
 * Service to check in and check out result sets Allows multiple result sets to
 * be checked out. Allows multiple threads to access it safely.
 */

public final class SearchResultService {
	static Logger logger = Logger.getLogger(SearchResultService.class.getName());

	private static SearchResultService _instance = null;
	private Hashtable resultsets = new Hashtable(200); // contains
														// SearchResultSet by
														// token (200 result
														// sets handled)
	/** Class name string */
	public static final String classStr = "o.m.d.m.SearchResultService";
	private long reaperInterval = 10800000; // 10800 seconds or 3 hours
	private Reaper reaper = null;

	private SearchResultService() {
		this.reaperInterval = PropertiesService.getLongProperty("searchresultservice.reaper.interval", 10800000);
		this.reaper = new Reaper();
		Thread t = new Thread(this.reaper);
		t.start();
		logger.debug("SearchResultService started ok.");
	}

	/**
	 * Singleton instance of this SearchResultService - should be accessed from
	 * DeusExMachina
	 */
	public static SearchResultService getInstance() {
		if (null == _instance) {
			_instance = new SearchResultService();
		}

		return _instance;
	}

	/** Destroy all objects worthy of destruction */
	public void destroy() {
		try {
			resultsets.clear();
		} catch (Exception oe) {
		}
		try {
			this.reaper.stopReaper();
		} catch (Exception oe) {
		}
		logger.debug("SearchResultService shutdown ok.");
	}

	/**
	 * Return the result set for this given token
	 * 
	 * @param token
	 */
	public SearchResultSet getResults(long token) {
		return (SearchResultSet) resultsets.get(new Long(token));
	}

	/**
	 * Add results to the resultset specified by this token
	 * 
	 * @param token
	 * @param batchResults
	 * @param source
	 * @param theoreticalAvailableResults
	 */
	public void addResults(long token, ArrayList batchResults, String source, int theoreticalAvailableResults, Map facetQueryCounts) {
		if (resultsets.containsKey(new Long(token))) {
			((SearchResultSet) resultsets.get(new Long(token))).addResults(batchResults, source, theoreticalAvailableResults, facetQueryCounts);
		}
	}

	/**
	 * Updates the resultset with this token, with the time taken by the source
	 * to complete its search
	 * 
	 * @param token
	 * @param time
	 * @param source
	 */
	public void calculateTimeTaken(long token, long time, String source) {
		if (resultsets.containsKey(new Long(token))) {
			((SearchResultSet) resultsets.get(new Long(token))).calculateTotalTime(time, source);
		}
	}

	/**
	 * Updates the resultset with this token, with the response code of this
	 * source
	 * 
	 * @param token
	 * @param source
	 * @param responseCode
	 */
	public void setResponseCode(long token, String source, String responseCode) {
		if (resultsets.containsKey(new Long(token))) {
			((SearchResultSet) resultsets.get(new Long(token))).setResponseCode(source, responseCode);
		}
	}

	/**
	 * Updates the resultset with this token, with the diagnostic of this source
	 * 
	 * @param token
	 * @param source
	 * @param diagnostic
	 */
	public void setDiagnostic(long token, String source, String diagnostic) {
		if (resultsets.containsKey(new Long(token))) {
			((SearchResultSet) resultsets.get(new Long(token))).setDiagnostic(source, diagnostic);
		}
	}

	/**
	 * Update the resultset with this token, with the thesaurus lookups
	 * 
	 * @param token
	 * @param narrowerTerms
	 * @param likeTerms
	 * @param broaderTerms
	 */
	public void updateThesauriTerms(long token, String[] narrowerTerms, String[] likeTerms, String[] broaderTerms) {
		if (resultsets.containsKey(new Long(token))) {
			((SearchResultSet) resultsets.get(new Long(token))).updateThesauriTerms(narrowerTerms, likeTerms, broaderTerms);
		}
	}

	/**
	 * Update the corrected terms in the resultset with this token
	 * 
	 * @param token
	 * @param correctedTerms
	 */
	public void updateCorrectedTerms(long token, String correctedTerms) {
		if (resultsets.containsKey(new Long(token))) {
			((SearchResultSet) resultsets.get(new Long(token))).updateCorrectedTerms(correctedTerms);
		}
	}

	/**
	 * Creates a new entry in the searchresult cache if it does not already
	 * exist
	 * 
	 * @param token
	 * @param set
	 * @exception Exception
	 */
	public void createEntry(long token, SearchResultSet set) throws Exception {
		if (!resultsets.containsKey(new Long(token))) {
			if (set != null) {
				resultsets.put(new Long(token), set);
			} else {
				throw new Exception("Cannot create SearchResultSet as there are no sources specified OR set is null");
			}
		}
	}

	/**
	 * Set the flag to complete for this resultset
	 * 
	 * @param token
	 */
	public void flagEntryComplete(long token) {
		if (resultsets.containsKey(new Long(token))) {
			SearchResultSet set = (SearchResultSet) resultsets.get(new Long(token));
			if (!set.isCompletedSet()) {
				logger.info("SUMMARY " + token + " " + set.getRawResultCount() + " " + (set.getTotalTimeTaken() / 1000f) + " 200");
			}
			set.flagComplete();
		}
	}

	private synchronized void removeEntry(long token) {
		resultsets.remove(new Long(token));
	}

	/** Manages the searchresults */
	private class Reaper implements java.lang.Runnable {
		boolean runReaper = true;

		public void stopReaper() {
			runReaper = false;
		}

		public void run() {
			while (runReaper) {
				try {
					long now = System.currentTimeMillis();

					for (Enumeration resKeys = resultsets.keys(); resKeys.hasMoreElements();) {
						Long thisToken = (Long) resKeys.nextElement();
						if (now - ((SearchResultSet) resultsets.get(thisToken)).getTimeCreated() >= reaperInterval) {
							removeEntry(thisToken.longValue());
						}
					}
				} catch (Exception oe) {
					logger.error("Reaper: Error while maintaining result sets", oe);
				}

				try {
					Thread.currentThread().sleep(reaperInterval);
				} catch (InterruptedException ie) {
				}
			}
		}
	}
}

// -- EOF

